package com.xtinno;

import cn.com.apollo.platformbase.response.ResponseResult;
import com.alibaba.fastjson2.JSONObject;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.xtinno.common.MDCUtils;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.StopWatch;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.util.Arrays;
import java.util.Map;

/**
 * Web层日志切面
 */
@Aspect
@Component
@Slf4j
public class WebLogAspect {
    /**
     * 定义切面
     */
    @Pointcut("execution(public * com.xtinno.controller..*.*(..))")
    public void webLog(){
    }
    @Around(value = "webLog()")
    public Object handleControllerMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        try {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            String traceId = MDCUtils.getOrGenerateTraceId(request);
            MDCUtils.setTraceId(traceId);

            StopWatch stopWatch = StopWatch.createStarted();
            String params = this.getRequestParams(request, joinPoint);
            log.info("【请求信息】请求url:{}，参数:{} ", request.getServletPath(), params);
            //继续下一个通知或目标方法调用，返回处理结果，如果目标方法发生异常，则 proceed 会抛异常.
            //如果在调用目标方法或者下一个切面通知前抛出异常，则不会再继续往后走.
            Object proceed = joinPoint.proceed(joinPoint.getArgs());

            stopWatch.stop();
            long watchTime = stopWatch.getTime();
//            MDCUtils.setTraceId(traceId);
            log.info("【响应结果】返回值={},耗时={} (毫秒)", proceed != null ? JSONObject.toJSONString(proceed) : "null", watchTime);
            return proceed;
        }
        catch (Exception e) {
            return ResponseResult.error(2, e.getMessage());
        }
        finally {
            MDC.clear();
        }
    }

    /**
     * 异常通知：目标方法发生异常的时候执行以下代码，此时返回通知@AfterReturning不会再触发
     * @param jp
     * @param ex
     */
    @AfterThrowing(pointcut = "webLog()", throwing = "ex")
    public void aspectAfterThrowing(JoinPoint jp, Exception ex) {
        String methodName = jp.getSignature().getName();
        log.error("【后置异常通知】{}方法异常：{}" ,methodName, ex.getMessage());
    }


    /**
     * 获取请求参数
     * @param request
     * @param joinPoint
     */
    private String getRequestParams(HttpServletRequest request, JoinPoint joinPoint) throws JsonProcessingException {
        StringBuilder params = new StringBuilder();
        // 获取 request parameter 中的参数
        Map<String, String[]> parameterMap = request.getParameterMap();
        if (parameterMap != null && !parameterMap.isEmpty()) {
            for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
                params.append(entry.getKey()).append(" = ").append(Arrays.toString(entry.getValue())).append(";");
            }
        }
        // 获取非 request parameter 中的参数
        Object[] objects = joinPoint.getArgs();
        for (Object arg : objects) {
            if (arg == null) {
                break;
            }
            String className = arg.getClass().getName().toLowerCase();
            String contentType = request.getHeader("Content-Type");
            if (contentType != null && contentType.contains("application/json")){
                // json 参数
                ObjectMapper mapper = new ObjectMapper();
                mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
                mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
                params.append(mapper.writeValueAsString(arg));
            }
        }
        return params.toString();
    }
}
